Overview

Use linked DMA channels to perform "scan" across multiple ADC input channels.

After each scan, use DMA scatter chain to write the converted ADC values to a separate output array for each ADC channel. The length of the output array to allocate for each ADC channel is determined by the sample_count in the example below.

See diagram below.

Channel configuration

  • DMA channel $i$ copies conesecutive SC1A configurations to the ADC SC1A register. Each SC1A configuration selects an analog input channel.
    • Channel $i$ is initially triggered by software trigger (i.e., DMA_SSRT = i), starting the ADC conversion for the first ADC channel configuration.
    • Loading of subsequent ADC channel configurations is triggered through minor loop linking of DMA channel $ii$ to DMA channel $i$.
  • DMA channel $ii$ is triggered by ADC conversion complete (i.e., COCO), and copies the output result of the ADC to consecutive locations in the result array.
    • Channel $ii$ has minor loop link set to channel $i$, which triggers the loading of the next channel SC1A configuration to be loaded immediately after the current ADC result has been copied to the result array.
  • After $n$ triggers of channel $i$, the result array contains $n$ ADC results, one result per channel in the SC1A table.
    • N.B., Only the trigger for the first ADC channel is an explicit software trigger. All remaining triggers occur through minor-loop DMA channel linking from channel $ii$ to channel $i$.
  • After each scan through all ADC channels is complete, the ADC readings are scattered using the selected "scatter" DMA channel through a major-loop link between DMA channel $ii$ and the "scatter" channel.

Device

Connect to device


In [1]:
from arduino_rpc.protobuf import resolve_field_values
from teensy_minimal_rpc import SerialProxy
import teensy_minimal_rpc.DMA as DMA
import teensy_minimal_rpc.ADC as ADC


# Disconnect from existing proxy (if available)
try:
    del proxy
except NameError:
    pass

proxy = SerialProxy()

In [2]:
dma_channel_scatter = 0
dma_channel_i = 1
dma_channel_ii = 2

Configure ADC sample rate, etc.


In [3]:
import arduino_helpers.hardware.teensy as teensy

# Set ADC parameters
proxy.setAveraging(16, teensy.ADC_0)
proxy.setResolution(16, teensy.ADC_0)
proxy.setConversionSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.setSamplingSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.update_adc_registers(
    teensy.ADC_0,
    ADC.Registers(CFG2=ADC.R_CFG2(MUXSEL=ADC.R_CFG2.B)))


Out[3]:
0

Pseudo-code to set DMA channel $i$ to be triggered by ADC0 conversion complete.

DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0  // Route ADC0 as DMA channel source.
DMAMUX0_CFGi[TRIG] = 0  // Disable periodic trigger.
DMAMUX0_CFGi[ENBL] = 1  // Enable the DMAMUX configuration for channel.

DMA_ERQ[i] = 1  // DMA request input signals and this enable request flag
                // must be asserted before a channel’s hardware service
                // request is accepted (21.3.3/394).
DMA_SERQ = i  // Can use memory mapped convenience register to set instead.

Set DMA mux source for channel 0 to ADC0


In [4]:
DMAMUX_SOURCE_ADC0 = 40  # from `kinetis.h`
DMAMUX_SOURCE_ADC1 = 41  # from `kinetis.h`

#    DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0  // Route ADC0 as DMA channel source.
#    DMAMUX0_CFGi[TRIG] = 0  // Disable periodic trigger.
#    DMAMUX0_CFGi[ENBL] = 1  // Enable the DMAMUX configuration for channel.
proxy.update_dma_mux_chcfg(dma_channel_ii,
                           DMA.MUX_CHCFG(SOURCE=DMAMUX_SOURCE_ADC0,
                                         TRIG=False,
                                         ENBL=True))

# DMA request input signals and this enable request flag
# must be asserted before a channel’s hardware service
# request is accepted (21.3.3/394).
#    DMA_SERQ = i
proxy.update_dma_registers(DMA.Registers(SERQ=dma_channel_ii))
proxy.enableDMA(teensy.ADC_0)

In [5]:
proxy.DMA_registers().loc['']


Out[5]:
full_name value short_description page
parent_name
ERQ 4 Enable Request Register 21.3.3/394
ERR 5 Error Register 21.3.14/409
INT 0 Interrupt Request Register 21.3.13/406
EEI 0 Enable Error Interrupt Register 21.3.4/397
HRS 0 Hardware Request Status Register 21.3.15/411

In [6]:
dmamux = DMA.MUX_CHCFG.FromString(proxy.read_dma_mux_chcfg(dma_channel_ii).tostring())
resolve_field_values(dmamux)[['full_name', 'value']]


Out[6]:
full_name value
parent_name
SOURCE 40
TRIG False
ENBL True

In [7]:
adc0 = ADC.Registers.FromString(proxy.read_adc_registers(teensy.ADC_0).tostring())
resolve_field_values(adc0)[['full_name', 'value']].loc[['CFG2', 'SC1A', 'SC3']]


Out[7]:
full_name value
parent_name
CFG2 CFG2.MUXSEL B
CFG2 CFG2.ADLSTS ADD_6_ADCK_CYCLES
CFG2 CFG2.ADHSC False
CFG2 CFG2.ADACKEN False
SC1A SC1A.COCO False
SC1A SC1A.DIFF False
SC1A SC1A.AIEN False
SC1A SC1A.ADCH 5
SC3 SC3.AVGE True
SC3 SC3.ADCO False
SC3 SC3.AVGS _8
SC3 SC3.CALF False
SC3 SC3.CAL False

Analog channel list

  • List of channels to sample.
  • Map channels from Teensy references (e.g., A0, A1, etc.) to the Kinetis analog pin numbers using the adc.CHANNEL_TO_SC1A_ADC0 mapping.

In [8]:
import re

import numpy as np
import pandas as pd
import arduino_helpers.hardware.teensy.adc as adc

# The number of samples to record for each ADC channel.
sample_count = 10

teensy_analog_channels = ['A0', 'A1', 'A0', 'A3', 'A0']
sc1a_pins = pd.Series(dict([(v, adc.CHANNEL_TO_SC1A_ADC0[getattr(teensy, v)])
                            for v in dir(teensy) if re.search(r'^A\d+', v)]))
channel_sc1as = np.array(sc1a_pins[teensy_analog_channels].tolist(), dtype='uint32')

Allocate and initialize device arrays

  • SD1A register configuration for each ADC channel in the channel_sc1as list.
    • Copy channel_sc1as list to device.
  • ADC result array
    • Initialize to zero.

In [9]:
proxy.free_all()

N = np.dtype('uint16').itemsize * channel_sc1as.size

# Allocate source array
adc_result_addr = proxy.mem_alloc(N)

# Fill result array with zeros
proxy.mem_fill_uint8(adc_result_addr, 0, N)

# Copy channel SC1A configurations to device memory
adc_sda1s_addr = proxy.mem_aligned_alloc_and_set(4, channel_sc1as.view('uint8'))

# Allocate source array
samples_addr = proxy.mem_alloc(sample_count * N)

tcds_addr = proxy.mem_aligned_alloc(32, sample_count * 32)
hw_tcds_addr = 0x40009000
tcd_addrs = [tcds_addr + 32 * i for i in xrange(sample_count)]
hw_tcd_addrs = [hw_tcds_addr + 32 * i for i in xrange(sample_count)]

# Fill result array with zeros
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)

# Create Transfer Control Descriptor configuration for first chunk, encoded
# as a Protocol Buffer message.
tcd0_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
                   BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
                   ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
                                       DSIZE=DMA.R_TCD_ATTR._16_BIT),
                   NBYTES_MLNO=channel_sc1as.size * 2,
                   SADDR=int(adc_result_addr),
                   SOFF=2,
                   SLAST=-channel_sc1as.size * 2,
                   DADDR=int(samples_addr),
                   DOFF=2 * sample_count,
                   DLASTSGA=int(tcd_addrs[1]),
                   CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=True))

# Convert Protocol Buffer encoded TCD to bytes structure.
tcd0 = proxy.tcd_msg_to_struct(tcd0_msg)

# Create binary TCD struct for each TCD protobuf message and copy to device
# memory.
for i in xrange(sample_count):
    tcd_i = tcd0.copy()
    tcd_i['SADDR'] = adc_result_addr
    tcd_i['DADDR'] = samples_addr + 2 * i
    tcd_i['DLASTSGA'] = tcd_addrs[(i + 1) % len(tcd_addrs)]
    tcd_i['CSR'] |= (1 << 4)
    proxy.mem_cpy_host_to_device(tcd_addrs[i], tcd_i.tostring())

# Load initial TCD in scatter chain to DMA channel chosen to handle scattering.
proxy.mem_cpy_host_to_device(hw_tcd_addrs[dma_channel_scatter],
                             tcd0.tostring())

print 'ADC results:', proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print 'Analog pins:', proxy.mem_cpy_device_to_host(adc_sda1s_addr, len(channel_sc1as) *
                                                   channel_sc1as.dtype.itemsize).view('uint32')


ADC results: [0 0 0 0 0]
Analog pins: [ 5 14  5  9  5]

Configure DMA channel $i$


In [10]:
ADC0_SC1A = 0x4003B000  # ADC status and control registers 1

sda1_tcd_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
                       BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
                       ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
                                           DSIZE=DMA.R_TCD_ATTR._32_BIT),
                       NBYTES_MLNO=4,
                       SADDR=int(adc_sda1s_addr),
                       SOFF=4,
                       SLAST=-channel_sc1as.size * 4,
                       DADDR=int(ADC0_SC1A),
                       DOFF=0,
                       DLASTSGA=0,
                       CSR=DMA.R_TCD_CSR(START=0, DONE=False))

proxy.update_dma_TCD(dma_channel_i, sda1_tcd_msg)


Out[10]:
0

Configure DMA channel $ii$


In [11]:
ADC0_RA = 0x4003B010  # ADC data result register
ADC0_RB = 0x4003B014  # ADC data result register


tcd_msg = DMA.TCD(CITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
                  BITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
                  ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
                                      DSIZE=DMA.R_TCD_ATTR._16_BIT),
                  NBYTES_MLNO=2,
                  SADDR=ADC0_RA,
                  SOFF=0,
                  SLAST=0,
                  DADDR=int(adc_result_addr),
                  DOFF=2,
                  DLASTSGA=-channel_sc1as.size * 2,
                  CSR=DMA.R_TCD_CSR(START=0, DONE=False,
                                    MAJORELINK=True,
                                    MAJORLINKCH=dma_channel_scatter))

proxy.update_dma_TCD(dma_channel_ii, tcd_msg)


Out[11]:
0

Trigger sample scan across selected ADC channels


In [12]:
# Clear output array to zero.
proxy.mem_fill_uint8(adc_result_addr, 0, N)
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)

# Software trigger channel $i$ to copy *first* SC1A configuration, which
# starts ADC conversion for the first channel.
#
# Conversions for subsequent ADC channels are triggered through minor-loop
# linking from DMA channel $ii$ to DMA channel $i$ (*not* through explicit
# software trigger).
print 'ADC results:'
for i in xrange(sample_count):
    proxy.update_dma_registers(DMA.Registers(SSRT=dma_channel_i))

    # Display converted ADC values (one value per channel in `channel_sd1as` list).
    print '  Iteration %s:' % i, proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')

print ''
print 'Samples by channel:'
# Trigger once per chunk
# for i in xrange(sample_count):
# proxy.update_dma_registers(DMA.Registers(SSRT=0))
device_dst_data = proxy.mem_cpy_device_to_host(samples_addr, sample_count * N)
pd.DataFrame(device_dst_data.view('uint16').reshape(-1, sample_count).T,
             columns=teensy_analog_channels)


ADC results:
  Iteration 0: [65535     2 65535 36365 65535]
  Iteration 1: [65532     2 65535 35941 65535]
  Iteration 2: [65532     2 65535 39189 65535]
  Iteration 3: [65532     2 65535 32148 65535]
  Iteration 4: [65527     2 65535 32701 65535]
  Iteration 5: [65528     2 65535 29713 65535]
  Iteration 6: [65532     2 65535 27901 65535]
  Iteration 7: [65531     2 65535 33160 65535]
  Iteration 8: [65524     2 65535 28725 65535]
  Iteration 9: [65533     2 65535 38502 65535]

Samples by channel:
Out[12]:
A0 A1 A0 A3 A0
0 65535 2 65535 36365 65535
1 65532 2 65535 35941 65535
2 65532 2 65535 39189 65535
3 65532 2 65535 32148 65535
4 65527 2 65535 32701 65535
5 65528 2 65535 29713 65535
6 65532 2 65535 27901 65535
7 65531 2 65535 33160 65535
8 65524 2 65535 28725 65535
9 65533 2 65535 38502 65535

In [ ]: